import type { TextEditor, Uri } from 'vscode';
import { ProgressLocation } from 'vscode';
import type { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { uncommitted, uncommittedStaged } from '../git/models/revision';
import { showGenericErrorMessage } from '../messages';
import type { AIExplainSource } from '../plus/ai/aiProviderService';
import { getBestRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
import { command } from '../system/-webview/command';
import { showMarkdownPreview } from '../system/-webview/markdown';
import { createMarkdownCommandLink } from '../system/commands';
import { Logger } from '../system/logger';
import { GlCommandBase } from './commandBase';
import { getCommandUri } from './commandBase.utils';
import type { CommandContext } from './commandContext';
import {
	isCommandContextViewNodeHasRepoPath,
	isCommandContextViewNodeHasRepository,
	isCommandContextViewNodeHasWorktree,
} from './commandContext.utils';

export interface ExplainWipCommandArgs {
	repoPath?: string | Uri;
	staged?: boolean;
	source?: AIExplainSource;
	worktreePath?: string;
}

@command()
export class ExplainWipCommand extends GlCommandBase {
	static createMarkdownCommandLink(args: ExplainWipCommandArgs): string {
		return createMarkdownCommandLink<ExplainWipCommandArgs>('gitlens.ai.explainWip:editor', args);
	}

	constructor(private readonly container: Container) {
		super(['gitlens.ai.explainWip', 'gitlens.ai.explainWip:editor', 'gitlens.ai.explainWip:views']);
	}

	protected override preExecute(context: CommandContext, args?: ExplainWipCommandArgs): Promise<void> {
		if (isCommandContextViewNodeHasWorktree(context)) {
			args = { ...args };
			args.repoPath = context.node.worktree.repoPath;
			args.worktreePath = context.node.worktree.path;
			args.source = args.source ?? { source: 'view', type: 'wip' };
		} else if (isCommandContextViewNodeHasRepository(context)) {
			args = { ...args };
			args.repoPath = context.node.repo.path;
			args.source = args.source ?? { source: 'view', type: 'wip' };
		} else if (isCommandContextViewNodeHasRepoPath(context)) {
			args = { ...args };
			args.repoPath = context.node.repoPath;
			args.source = args.source ?? { source: 'view', type: 'wip' };
		}

		return this.execute(context.editor, context.uri, args);
	}

	async execute(editor?: TextEditor, uri?: Uri, args?: ExplainWipCommandArgs): Promise<void> {
		args = { ...args };

		// Get the diff of working changes
		const diffService = await this.getDiff(editor, uri, args);
		if (diffService?.getDiff === undefined) {
			void showGenericErrorMessage('Unable to get diff service');
			return;
		}

		try {
			// If args?.staged is undefined, get all changes (staged and unstaged)?
			let stagedLabel;
			let to;
			if (args?.staged === true) {
				stagedLabel = 'Staged';
				to = uncommittedStaged;
			} else if (args?.staged === false) {
				stagedLabel = 'Unstaged';
				to = uncommitted;
			} else {
				stagedLabel = 'Uncommitted';
				to = '';
			}

			const diff = await diffService.getDiff(to, undefined);
			if (!diff?.contents) {
				void showGenericErrorMessage('No working changes found to explain');
				return;
			}

			// Get worktree info
			let worktreeInfo = '';
			let worktreeDisplayName = '';

			if (args?.worktreePath) {
				// Get the worktree name if available
				const worktrees = await this.container.git.worktrees(args.worktreePath)?.getWorktrees();
				const worktree = worktrees?.find(w => w.path === args.worktreePath);

				if (worktree) {
					worktreeInfo = ` in ${worktree.name}`;
					worktreeDisplayName = ` (${worktree.name})`;
				} else {
					worktreeInfo = ` in worktree`;
					worktreeDisplayName = ` (${args.worktreePath})`;
				}
			}

			// Call the AI service to explain the changes
			const result = await this.container.ai.explainChanges(
				{
					diff: diff.contents,
					message: `${stagedLabel} working changes${worktreeInfo}`,
				},
				{
					...args.source,
					source: args.source?.source ?? 'commandPalette',
					type: 'wip',
				},
				{
					progress: {
						location: ProgressLocation.Notification,
						title: `Explaining working changes${worktreeInfo}...`,
					},
				},
			);

			if (result == null) {
				void showGenericErrorMessage('Unable to explain working changes');
				return;
			}

			const title = `Working Changes Summary${worktreeDisplayName}`;
			const content = `# ${title}\n\n> Generated by ${result.model.name}\n\n## ${stagedLabel} Changes\n\n${result.parsed.summary}\n\n${result.parsed.body}`;

			void showMarkdownPreview(content);
		} catch (ex) {
			Logger.error(ex, 'ExplainWipCommand', 'execute');
			void showGenericErrorMessage('Unable to explain working changes');
		}
	}

	private async getDiff(editor?: TextEditor, uri?: Uri, args?: ExplainWipCommandArgs) {
		let diffService;
		if (args?.worktreePath) {
			diffService = this.container.git.diff(args.worktreePath);
		} else if (args?.repoPath) {
			diffService = this.container.git.diff(args.repoPath);
		} else {
			uri = getCommandUri(uri, editor);
			const gitUri = uri != null ? await GitUri.fromUri(uri) : undefined;
			const repository = await getBestRepositoryOrShowPicker(
				gitUri,
				editor,
				'Explain Working Changes',
				'Choose which repository to explain working changes from',
			);

			diffService = repository?.git.diff();
		}

		return diffService;
	}
}
